package com.junmoo.www.auth.config.authentication;

import com.alibaba.fastjson.JSONObject;
import com.junmoo.www.auth.config.CloudUserDetailsService;
import com.junmoo.www.entity.SysUser;
import com.junmoo.www.tool.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 此处用于从请求的信息中载入验证信息（即将header中的token包装成Authentication并进行验证）
 * 还有一个作用： 禁用session ,因为默认实现是 WebSessionServerSecurityContextRepository
 */
@Component
@Slf4j
public class CloudContextRepository  implements ServerSecurityContextRepository {
    @Autowired
    private CloudUserDetailsService cloudUserDetailsService;
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 在 AuthenticationWebFilter 中被调用;
     * 主要作用：在登陆成功后，设置SecurityContext
     * @param exchange
     * @param context
     * @return
     */
    @Override
    public Mono<Void> save(ServerWebExchange exchange, SecurityContext context) {
        log.info("------------ContextRepository----save------: ");
        if(context ==null){
            UserDetails user =  cloudUserDetailsService.findByUsername("admin").block();
            Authentication authentication = new UsernamePasswordAuthenticationToken(user,"",user.getAuthorities() );
            SecurityContext securityContext = new SecurityContextImpl(authentication);
            return Mono.empty().then().subscriberContext( ReactiveSecurityContextHolder.withSecurityContext( Mono.just(securityContext) ) );
        }
        return  Mono.empty().then().subscriberContext( ReactiveSecurityContextHolder.withSecurityContext( Mono.just(context) ));
    }

    /**
     *
     * 在 ReactorContextWebFilter( AuthenticationWebFilter 的上一个 filter ) 中被调用;
     * 默认实现是： WebSessionServerSecurityContextRepository； 获取对应 session 中的 SecurityContext 返回，若session==null || SecurityContext==null,则返回空。
     * 主要作用：获取 SecurityContext
     * 注意 ：这里可以使用 redis key 代替 session ，使其适用于分布式环境，
     * 一个客户端一个session，session 里保存 SecurityContext 对象; 一个用户一个 redis key ，redis key 也保存SecurityContext 对象
     *
     * @param exchange
     * @return
     */
    @Override
    public Mono<SecurityContext> load(ServerWebExchange exchange) {
        log.info("------------ContextRepository----load------");
        String requestTokenHeader = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        if (requestTokenHeader == null || !requestTokenHeader.startsWith("Bearer ")) {
            return Mono.empty();
        }
        String requestToken = requestTokenHeader.substring(7);
        SysUser sysUser = JSONObject.parseObject(JwtUtils.getSubject(requestToken), SysUser.class);
        if(sysUser==null) {
            return Mono.empty();
        }
        return cloudUserDetailsService.findByUsername(sysUser.getUsername())
                .switchIfEmpty(Mono.empty())
                .flatMap(user->{
                    String redis_refresh_token = (String)redisTemplate.opsForHash().get(user.getUsername(),"token");
                    if(JwtUtils.isTokenExpired(requestToken) ){
                        if(redis_refresh_token==null ){
                            return Mono.empty();
                        }
                    }
                    if(redis_refresh_token==null ){
                        String refresh_token = JwtUtils.generateToken(JSONObject.toJSONString(user),3*60*60L);
                        Map<String,Object> map = new HashMap();
                        map.put("token",refresh_token);
                        map.put("user",user);
                        map.put("role",new String[]{"admin"});
                        map.put("auth",user.getAuthorities());
                        redisTemplate.opsForHash().putAll(user.getUsername(),map);
                        redisTemplate.expire(user.getUsername(),2, TimeUnit.HOURS);
                    }
                    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user,"",user.getAuthorities() );
                    authenticationToken.setDetails(user);
                    SecurityContext context = new SecurityContextImpl(authenticationToken);
                    return Mono.just(context);
                });
    }
}
